﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Xml.Serialization;
using System.ComponentModel;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace Jemgine.Data
{
    public interface EntityElement
    {
        bool PointInside(Vector2 Point);
        Engine.Selection.Types SelectionType();
        EntityElement Clone();
        void SetOwner(EntityRecord Owner);

        void Initialize(EntityRecord Owner, ContentManager Content);
        void Destroy();

        Data.Bone BoneReference { get; set; }
        int BoneID { get; set; }
    }

    public enum Facings
	{
		Left,
		Right
	}

    class ComponentListSearcher : Engine.TweakableCollectionSearch
    {
        public object Search(object Collection, string str)
        {
            foreach (var Element in ( Collection as List<EntityElement> ))
            {
                var Component = Element as Engine.EntityComponent;
                if (Component != null && String.Compare(Component.Name, str, true) == 0) return Component;
            }
            return null;
        }
    }

    public class ElementList : List<EntityElement>
    {
        [ContentSerializerIgnore]
        internal EntityRecord Owner;

        new public void Add(EntityElement E)
        {
            E.SetOwner(Owner);
            base.Add(E);
        }

        new public void AddRange(IEnumerable<EntityElement> Range)
        {
            foreach (var Element in Range)
                Add(Element);
        }

        internal ElementList(EntityRecord Owner)
        {
            this.Owner = Owner;
        }

        public ElementList() { }
    }

    [Jemgine.Engine.Category("Primitive"), Jemgine.Engine.Node]
    public class EntityRecord : ScriptNode, EditorDrawable, MapElement, ReferenceTarget, Tintable
	{
        public enum State
        {
            Limbo,
            Initialized,
            Dead,

            PreCreated,
        }

        public bool Hidden { get; set; }

        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public State state { get; internal set; }

        public enum _DrawDepth
        {
            BackGround,
            MiddleGround,
            ForeGround,
        }

        [Microsoft.Xna.Framework.Content.ContentSerializer(Optional = true)]
        public _DrawDepth DrawDepth { get; set; }

		public Facings Facing { get; set; }
        public float Angle { get; set; }
        [ContentSerializer(Optional = true)]
        public float Scale { get; set; }

        [ContentSerializer(Optional = true)]
        public Vector4 Tint { get; set; }

        public Matrix GetTransform()
        {
            return ((Facing == Data.Facings.Left) ? Matrix.CreateScale(-1.0f, 1.0f, 1.0f) : Matrix.Identity) *
                Matrix.CreateScale(Scale) *
                Matrix.CreateRotationZ(Angle) *
                Matrix.CreateTranslation(Position.X, Position.Y, 0);
        }

        [Engine.TweakableCollection("Components", typeof(ComponentListSearcher))]
        public ElementList AttachedElements;

        public Vector2 TransformToWorldSpace(Vector2 V)
        {
            return Vector2.Transform(V, Matrix.CreateRotationZ(Angle))
                + Position;
        }

        public Vector2 TransformToLocalSpace(Vector2 V)
        {
            V -= Position;
            return Vector2.Transform(V, Matrix.CreateRotationZ(-Angle));
        }

        #region MapElementMembers

        new public Jemgine.Engine.Selection.Types SelectionType()
        {
            return Jemgine.Engine.Selection.Types.MapElement;
            //return Jemgine.Engine.Selection.Types.Entity;
        }

        new public MapElement Clone()
        {
            EntityRecord NewRecord = (EntityRecord)base.Clone();
            foreach (EntityElement Element in AttachedElements)
                NewRecord.AttachedElements.Add(Element.Clone());
            return NewRecord;
        }
        
        public void AssignGlobalBoneIDs()
        {
            int NextID = 0;
            foreach (var Element in AttachedElements)
                if (Element is Skeleton)
                    NextID = (Element as Skeleton).AssignGlobalBoneIDs(NextID);
        }


        new public void EnumerateTargets(List<InputTerminal> Inputs, List<ReferenceTarget> Targets)
        {
            base.EnumerateTargets(Inputs, Targets);
            foreach (EntityElement Element in AttachedElements)
            {
                if (Element is ScriptNode)
                    (Element as ScriptNode).EnumerateTargets(Inputs, Targets);
                else if (Element is ReferenceTarget)
                    Targets.Add(Element as ReferenceTarget);

                if (Element is Data.Skeleton)
                {
                    var Skel = Element as Data.Skeleton;
                    foreach (var Bone in Skel.Bones)
                        Targets.Add(Bone as ReferenceTarget);
                }
            }
        }

        new public void PreProcess()
        {
            base.PreProcess();
            AssignGlobalBoneIDs();
            foreach (EntityElement Element in AttachedElements)
            {
                if (Element is ScriptNode)
                    (Element as ScriptNode).PreProcess();
                else if (Element is EditorPolygon)
                    (Element as EditorPolygon).UpdateDecomposedCache();

                if (Element.BoneReference != null) Element.BoneID = Element.BoneReference.G_ID;
                else Element.BoneID = -1;
            }
        }

        //Seems to be called by the Delete operation to make sure references to deleted objects don't get serialized.
        new public void ClearAllIDs()
        {
            base.ClearAllIDs();
            foreach (var Element in AttachedElements)
            {
                if (Element is ScriptNode)
                    (Element as ScriptNode).ClearAllIDs();
                if (Element is ReferenceTarget)
                    (Element as ReferenceTarget).ReferenceID = -1;
                if (Element is Skeleton)
                    (Element as Skeleton).ClearAllIDs();
            }
        }

        new public void PostProcess(Vector2 Offset, MapData Map, Engine.Simulation Sim)
        {
            base.PostProcess(Offset, Map, Sim);
            AttachedElements.Owner = this;
            var Bones = new List<Bone>();
            this.EnumerateBones(Bones);
            foreach (EntityElement Element in AttachedElements)
            {
                Element.SetOwner(this);
                if (Element.BoneID != -1 && Element.BoneID < Bones.Count)
                    Element.BoneReference = Bones[Element.BoneID];
                Engine.EntityComponent Component = Element as Engine.EntityComponent;
                if (Component != null)
                {
                    Component.PostProcess(Vector2.Zero, Map, Sim);
                }

            }
        }

        public void EnumerateBones(List<Bone> Bones)
        {
            foreach (EntityElement Element in AttachedElements)
            {
                if (Element is Skeleton) (Element as Skeleton).EnumerateBones(Bones);
            }
        }

        public void SetBoneReferences(List<Bone> Bones)
        {
            foreach (EntityElement Element in AttachedElements)
                if (Element.BoneID != -1) Element.BoneReference = Bones[Element.BoneID];
        }
        

        #endregion

        public EntityRecord()
        {
            AttachedElements = new ElementList(this);
            AddOutput("OnSpawned", null);
            AddOutput("OnDestroyed", null);

            state = State.Limbo;
            DrawDepth = _DrawDepth.MiddleGround;
            Scale = 1.0f;
            Facing = Facings.Right;
            Tint = Vector4.One;
        }

        public T GetComponent<T>() where T : class
        {
            for (int i = 0; i < AttachedElements.Count; ++i)
                if (AttachedElements[i] is T) return AttachedElements[i] as T;
            return null;
        }

        public override void Initialize(Microsoft.Xna.Framework.Content.ContentManager Content)
        {
            //if (!String.IsNullOrEmpty(Skeleton))
            //    _skeleton = Content.Load<Jemgine.Model.Model>(Skeleton);

            for (int i = 0; i < AttachedElements.Count; ++i)
                AttachedElements[i].Initialize(this, Content);
            OutputTerminals[0].Fire(ScriptPayload.CreatePayload(this));
        }

        override public void SetActive(bool Active)
        {
            Enabled = Active;
            for (int i = 0; i < AttachedElements.Count; ++i)
                if (AttachedElements[i] is Jemgine.Engine.EntityComponent) 
                    (AttachedElements[i] as Jemgine.Engine.EntityComponent).SetActive(Active);
        }

        public void Destroy()
        {
            for (int i = 0; i < AttachedElements.Count; ++i)
                AttachedElements[i].Destroy();
            OutputTerminals[1].Fire(ScriptPayload.CreatePayload(this));
        }
        
        #region EditorDrawable Members

        public void DrawSelectedAttachment(EditorDrawData Context, int ID, SelectionType Type)
        {
            Context.Renderer.PushMatrix(Matrix.CreateTranslation(Position.X, Position.Y, 0.0f));
            (AttachedElements[ID] as EditorDrawable).Draw(ID, Context, Type);
            Context.Renderer.PopMatrix();
        }

        public void DrawVertexSelection(EditorDrawData Context, int PolyID, int VertexID, SelectionType Type)
        {
            Context.Renderer.PushMatrix(Matrix.CreateTranslation(Position.X, Position.Y, 0.0f));
            (AttachedElements[PolyID] as EditorDrawable).Draw(PolyID, Context, Data.SelectionType.WorkingSet);


            var Verts = AttachedElements[PolyID] as HasVerticies;
            if (Verts != null)
            {
                for (int i = 0; i < Verts.Count; ++i)
                    Context.Renderer.DrawPoint(Verts.GetVertex(i).Position);
                Context.Renderer.Color = EditorDrawData.DrawColor(Type);


                Context.Renderer.DrawPoint(Verts.GetVertex(VertexID).Position);
            }
            
            Context.Renderer.PopMatrix();
        }

        new public void Draw(int ID, EditorDrawData Context, SelectionType Selected)
        {
            base.Draw(ID, Context, Selected);
            Context.Renderer.PushMatrix(Matrix.CreateTranslation(Position.X, Position.Y, 0.0f));

            //try
            //{
            //    if (!String.IsNullOrEmpty(Skeleton) && _skeleton == null)
            //        _skeleton = Context.Content.Load<Jemgine.Model.Model>(Skeleton);
            //}
            //catch (ContentLoadException) { _skeleton = null; }

            //if (_skeleton != null)
            //    Context.Renderer.DrawSkeleton(_skeleton);

            Data.SelectionType ChildSelectionType = Data.SelectionType.NotSelected;
            if (Selected == Data.SelectionType.Selected ||
                Selected == Data.SelectionType.WorkingSet) ChildSelectionType = Data.SelectionType.WorkingSet;
            if (Selected == Data.SelectionType.MultiSelected) ChildSelectionType = Data.SelectionType.MultiSelected;

            for (int Element = AttachedElements.Count - 1; Element >= 0; --Element)
            {
                var _E = AttachedElements[Element];
                if (_E.BoneReference != null)
                    Context.Renderer.PushMatrix(_E.BoneReference.Transform);
                (_E as EditorDrawable).Draw(Element, Context, ChildSelectionType);
                if (_E.BoneReference != null)
                    Context.Renderer.PopMatrix();
            }

            for (int I = 0; I < AttachedElements.Count; ++I)
            {
                if (AttachedElements[I] is Engine.EntityComponent)
                   Context.Renderer.DrawLine(Vector2.Zero, (AttachedElements[I] as Engine.EntityComponent).GetPosition(), 0.2f);
            }

            Context.Renderer.PopMatrix();
        }

        public void DrawOutlinedElements(EditorDrawData Context, Func<EntityElement, bool> Filter)
        {
            Context.Renderer.PushMatrix(Matrix.CreateTranslation(Position.X, Position.Y, 0.0f));
            foreach (var Element in AttachedElements)
            {
                if (!Filter(Element)) continue;
                if (Element.BoneReference != null) Context.Renderer.PushMatrix(Element.BoneReference.Transform);
                (Element as EditorDrawable).Draw(0, Context, Data.SelectionType.Selected);
                if (Element.BoneReference != null) Context.Renderer.PopMatrix();
            }
            Context.Renderer.PopMatrix();
        }

        #endregion

        public void Reset(EntityRecord To)
        {
            this.CopyProperties(To);

            for (int i = 0; i < this.AttachedElements.Count && i < To.AttachedElements.Count; ++i)
            {
                var A = this.AttachedElements[i] as Engine.EntityComponent;
                var B = To.AttachedElements[i] as Engine.EntityComponent;

                if (A == null || B == null) continue;
                A.Reset(B);
            }

            var Physics = this.GetComponent<Engine.Physics>();
            if (Physics != null && Physics.PhysicsBody != null)
            {
                Physics.PhysicsBody.Position = this.Position;
                Physics.PhysicsBody.Rotation = this.Angle;
            }
        }

        public void ForEachAttachedReverse(Action<EntityElement> Callback)
        {
            for (int I = AttachedElements.Count - 1; I >= 0; --I) Callback(AttachedElements[I]);
        }

        #region Tintable Members

        public void SetTint(Vector4 Tint)
        {
            this.Tint = Tint;
        }

        public Vector4 GetTint()
        {
            return Tint;
        }

        #endregion
    }
}
